Elastic Beanstalkのマネージドプラットフォーム更新が失敗し続けていたらどうする?

Elastic Beanstalkのマネージドプラットフォーム更新が失敗し続けていたらどうする?

Elastsic Beanstalkの環境のアップデートが知らぬ間に失敗し続けていたら、マネージドプラットフォーム更新を行うロールの権限を確認してみましょう。
Clock Icon2022.07.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。CX事業本部Delivery部MADグループのきんじょーです。

担当している案件で、Elastic Beanstalkでデプロイしているアプリケーションの環境に、自動的に更新が走り失敗を繰り返しているという事象が発生しました。 同じ問題を抱える人のため、調査結果と対応方法についてブログにまとめます。

先に結論

  • Elastic Beanstalkのサービスロールには、通常更新マネージドプラットフォーム更新用2種類があります。
  • 通常更新と、マネージドプラットフォーム更新用のサービスロールには同じロールを設定しましょう
  • 現在、マネージドプラットフォーム更新に適切なポリシーはAWSElasticBeanstalkManagedUpdatesCustomerRolePolicyです
  • どちらのサービスロールも明示的に指定しない場合、何も気にする必要はありません

マネージドプラットフォーム更新とは?

AWSのドキュメントによると以下のように記載されています。

AWS Elastic Beanstalk は定期的にプラットフォームの更新をリリースし、修正やソフトウェア更新、新機能を提供しています。マネージドプラットフォーム更新機能により、予定済みのメンテナンス期間中に、環境を自動的に最新バージョンのプラットフォームに更新できます。更新プロセス中も、アプリケーションは能力を低減させることなく稼動状態に保たれます。マネージド更新は、単一インスタンス環境とロードバランシング環境の両方で利用できます。

参考: マネージドプラットフォーム更新

設定をオンにすると、プラットフォームのマイナーバージョンの更新やパッチ適用のため、自動的にAWS側でデプロイが開始されます。

実行されたマネージド更新はElastic Beanstalkの管理コンソールから確認できます。 managed-update-list

失敗の原因はIAMの権限不足

以下はデプロイ時のElastic Beanstalkのイベントログです。
デプロイが開始されAWSEBEC2LaunchTemplateというリソースの作成でエラーが発生しています。 eb-deploy-failed

CloudFormation側のログを確認すると、起動テンプレートの作成が許可されていない旨のエラーが出力されています。 unauthorized-operation

IAMが怪しいと分かったので、マネージドプラットフォーム更新時に使用されるロールのポリシーを詳しく見ていきます。

Elastic Beanstalkで使用するロール

Elastic Beanstalkは3種類のIAMロールを設定可能です。

  1. サービスロール
    • 通常のサービスロール
      • Elastic Beanstalkのサービス自体が、デプロイ時にAWSのリソースを操作する際のロール
    • マネージドプラットフォーム更新用サービスロール
      • マネージドプラットフォーム更新で、環境を更新する時にのみ使用されるロール
  2. IAMインスタンスプロファイル
    • アプリケーションがデプロイされるEC2インスタンスに紐付けられるロール

service-role-and-instance-profile managed-update-role

マネージドプラットフォーム更新に使用されるサービスロール

サービスロールの指定は任意の設定項目です。 設定しなければaws-elasticbeanstalk-service-roleというロールが自動的に作成され、通常時とマネージド更新時の両方で使用されます。

マネージドプラットフォーム更新用サービスロールは、ebextentionsで明示的に指定可能です。 service-role-setting-in-ebextensions

参考: aws:elasticbeanstalk:managedactions

設定可能な値は、サービスロールと同じか、AWSServiceRoleForElasticBeanstalkManagedUpdatesという固定の値で、固定値の場合同じ名前のロールが自動作成されます。
以下はebextensionsで設定する際の例です。

option_settings:
  aws:elasticbeanstalk:managedactions:
    ServiceRoleForManagedUpdates: AWSServiceRoleForElasticBeanstalkManagedUpdates

そして、
⚠️上記のようにebextensionsで AWSServiceRoleForElasticBeanstalkManagedUpdatesを指定しないでください!!⚠️
理由は以下の通りです。

自動作成されるAWSServiceRoleForElasticBeanstalkManagedUpdatesの権限が不足している

このロールには、AWSElasticBeanstalkManagedUpdatesServiceRolePolicyというAWS管理ポリシーがアタッチされています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowPassRoleToElasticBeanstalkAndDownstreamServices",
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "*",
            "Condition": {
                "StringLikeIfExists": {
                    "iam:PassedToService": [
                        "elasticbeanstalk.amazonaws.com",
                        "ec2.amazonaws.com",
                        "autoscaling.amazonaws.com",
                        "elasticloadbalancing.amazonaws.com",
                        "ecs.amazonaws.com",
                        "cloudformation.amazonaws.com"
                    ]
                }
            }
        },
        {
            "Sid": "SingleInstanceAPIs",
            "Effect": "Allow",
            "Action": [
                "ec2:releaseAddress",
                "ec2:allocateAddress",
                "ec2:DisassociateAddress",
                "ec2:AssociateAddress"
            ],
            "Resource": "*"
        },
        {
            "Sid": "ECS",
            "Effect": "Allow",
            "Action": [
                "ecs:RegisterTaskDefinition",
                "ecs:DeRegisterTaskDefinition",
                "ecs:List*",
                "ecs:Describe*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "ElasticBeanstalkAPIs",
            "Effect": "Allow",
            "Action": [
                "elasticbeanstalk:*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "ReadOnlyAPIs",
            "Effect": "Allow",
            "Action": [
                "cloudformation:Describe*",
                "cloudformation:List*",
                "ec2:Describe*",
                "autoscaling:Describe*",
                "elasticloadbalancing:Describe*",
                "logs:DescribeLogGroups",
                "sns:GetTopicAttributes",
                "sns:ListSubscriptionsByTopic"
            ],
            "Resource": "*"
        },
        {
            "Sid": "ASG",
            "Effect": "Allow",
            "Action": [
                "autoscaling:AttachInstances",
                "autoscaling:CreateAutoScalingGroup",
                "autoscaling:CreateLaunchConfiguration",
                "autoscaling:DeleteAutoScalingGroup",
                "autoscaling:DeleteLaunchConfiguration",
                "autoscaling:DeleteScheduledAction",
                "autoscaling:DetachInstances",
                "autoscaling:PutNotificationConfiguration",
                "autoscaling:PutScalingPolicy",
                "autoscaling:PutScheduledUpdateGroupAction",
                "autoscaling:ResumeProcesses",
                "autoscaling:SuspendProcesses",
                "autoscaling:TerminateInstanceInAutoScalingGroup",
                "autoscaling:UpdateAutoScalingGroup"
            ],
            "Resource": [
                "arn:aws:autoscaling:*:*:launchConfiguration:*:launchConfigurationName/awseb-e-*",
                "arn:aws:autoscaling:*:*:autoScalingGroup:*:autoScalingGroupName/awseb-e-*",
                "arn:aws:autoscaling:*:*:launchConfiguration:*:launchConfigurationName/eb-*",
                "arn:aws:autoscaling:*:*:autoScalingGroup:*:autoScalingGroupName/eb-*"
            ]
        },
        {
            "Sid": "CFN",
            "Effect": "Allow",
            "Action": [
                "cloudformation:CreateStack",
                "cloudformation:CancelUpdateStack",
                "cloudformation:DeleteStack",
                "cloudformation:GetTemplate",
                "cloudformation:UpdateStack"
            ],
            "Resource": [
                "arn:aws:cloudformation:*:*:stack/awseb-e-*",
                "arn:aws:cloudformation:*:*:stack/eb-*"
            ]
        },
        {
            "Sid": "EC2",
            "Effect": "Allow",
            "Action": [
                "ec2:TerminateInstances"
            ],
            "Resource": "arn:aws:ec2:*:*:instance/*",
            "Condition": {
                "StringLike": {
                    "ec2:ResourceTag/aws:cloudformation:stack-id": [
                        "arn:aws:cloudformation:*:*:stack/awseb-e-*",
                        "arn:aws:cloudformation:*:*:stack/eb-*"
                    ]
                }
            }
        },
        {
            "Sid": "S3Obj",
            "Effect": "Allow",
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:GetObjectVersion",
                "s3:GetObjectVersionAcl",
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:PutObjectVersionAcl"
            ],
            "Resource": "arn:aws:s3:::elasticbeanstalk-*/*"
        },
        {
            "Sid": "S3Bucket",
            "Effect": "Allow",
            "Action": [
                "s3:GetBucketLocation",
                "s3:GetBucketPolicy",
                "s3:ListBucket",
                "s3:PutBucketPolicy"
            ],
            "Resource": "arn:aws:s3:::elasticbeanstalk-*"
        },
        {
            "Sid": "CWL",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:DeleteLogGroup",
                "logs:PutRetentionPolicy"
            ],
            "Resource": "arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk/*"
        },
        {
            "Sid": "ELB",
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:RegisterTargets",
                "elasticloadbalancing:DeRegisterTargets",
                "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
                "elasticloadbalancing:RegisterInstancesWithLoadBalancer"
            ],
            "Resource": [
                "arn:aws:elasticloadbalancing:*:*:targetgroup/awseb-*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/awseb-e-*",
                "arn:aws:elasticloadbalancing:*:*:targetgroup/eb-*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/eb-*"
            ]
        },
        {
            "Sid": "SNS",
            "Effect": "Allow",
            "Action": [
                "sns:CreateTopic"
            ],
            "Resource": "arn:aws:sns:*:*:ElasticBeanstalkNotifications-Environment-*"
        }
    ]
}

上記のように、このポリシーでは起動テンプレートの作成(ec2:CreateLaunchTemplate)の許可が含まれていません。 一方、起動設定(autoscaling:CreateLaunchConfiguration)は許可されているため、Elastic Beanstalkの環境が起動設定でAuto Scaling Groupを組んでいた頃から更新されていないものかと想像しています。

このマネージドプラットフォーム更新用のロールを自動作成していたため、権限が足りず起動テンプレートの作成に失敗していた。というのが本件の原因でした。

対応方法

以下のどちらかで対応可能です。

1.サービスロールを独自に作成し、通常更新と、マネージドプラットフォーム更新用のサービスロールに同じロールを設定する

この場合、サービスロールにはAWSElasticBeanstalkManagedUpdatesCustomerRolePolicyという、AWS管理ポリシーをアタッチしてください。 AWS管理のロールではなく、サービスロールを独自に作成したい方はこちらの方法が良いでしょう。

2.サービスロール設定で、通常・マネージドプラットフォーム更新のどちらも設定をしない

何も指定しない場合、aws-elasticbeanstalk-service-roleというロールが自動作成され、このロールには適切なポリシーがアタッチされています。 AWSが用意するロールで問題ない場合、こちらの方が手間少ないです。

まとめ

  • Elastic Beanstalkのサービスロールには、通常更新マネージドプラットフォーム更新用2種類があります。
  • 通常更新と、マネージドプラットフォーム更新用のサービスロールには同じロールを設定しましょう
    • 別で設定するとロールを設定すると不十分な権限のポリシーAWSElasticBeanstalkServicerRolePolicyをアタッチしたサービスロールが自動作成され「起動テンプレート」を作成する際にエラーを起こします。
  • 現在、マネージドプラットフォーム更新に適切なポリシーはAWSElasticBeanstalkManagedUpdatesCustomerRolePolicyです
    • サービスロールには、上記ポリシーを付与されていることを確認しましょう。
  • どちらのサービスロールも明示的に指定しない場合、何も気にする必要はありません
    • 自動的に上記ポリシーが付与されたロールが自動作成され、通常とマネージドプラットフォーム更新の両方で使用されます。

最後に

Elastic Beanstalkは、インフラとアプリケーションのデプロイや運用監視を楽にしてくれます。一方、PaaSとして提供されているので、今回のように内部的に作成されるポリシーの内容など手を入れることが出来ないため、トレードオフとして割り切る必要があります。 が、それにしても今回はアタッチするIAMポリシーが古いまま更新がされていないように見えるので、もう少し調べて確認してみたいと思います。

この記事が少しでも誰かの役に立つと幸いです。
以上。CX事業本部Delivery部MADグループのきんじょーでした。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.